/**
 * 
 */
package org.swing.utility.net.server;

import java.io.IOException;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.swing.utility.net.client.OSCReceiver;
import org.swing.utility.net.exception.OSCException;
import org.swing.utility.net.imp.OSCBidi;
import org.swing.utility.net.imp.OSCChannel;
import org.swing.utility.net.imp.OSCListener;
import org.swing.utility.net.message.OSCMessage;
import org.swing.utility.net.message.OSCTransmitter;
import org.swing.utility.net.packet.OSCPacket;
import org.swing.utility.net.packet.OSCPacketCodec;
import org.swing.utility.net.util.NetUtil;

/**
 * @author lqnhu
 *
 */
public abstract class OSCServer implements OSCBidi {
	protected OSCPacketCodec defaultCodec;
	private final String protocol;

	protected OSCServer(OSCPacketCodec c, String protocol) {
		defaultCodec = c;
		this.protocol = protocol;
	}

	/**
	 * Creates a new instance of an <code>OSCServer</code>, using default codec
	 * and a specific transport protocol. It picks an arbitrary free port and
	 * uses the local machine's IP. To determine the resulting port, you can use
	 * <code>getLocalAddress</code> afterwards.
	 *
	 * @param protocol
	 *            the protocol to use, currently either <code>UDP</code> or
	 *            <code>TCP</code>
	 * @return the newly created server
	 *
	 * @throws IOException
	 *             if a networking error occurs while creating the socket
	 * @throws IllegalArgumentException
	 *             if an illegal protocol is used
	 *
	 * @see OSCChannel#UDP
	 * @see OSCChannel#TCP
	 * @see #getLocalAddress
	 */
	public static OSCServer newUsing(String protocol) throws IOException {
		return newUsing(OSCPacketCodec.getDefaultCodec(), protocol);
	}

	/**
	 * Creates a new instance of an <code>OSCServer</code>, using a specific
	 * codec and transport protocol. It picks an arbitrary free port and uses
	 * the local machine's IP. To determine the resulting port, you can use
	 * <code>getLocalAddress</code> afterwards.
	 *
	 * @param c
	 *            the codec to use
	 * @param protocol
	 *            the protocol to use, currently either <code>UDP</code> or
	 *            <code>TCP</code>
	 * @return the newly created server
	 *
	 * @throws IOException
	 *             if a networking error occurs while creating the socket
	 * @throws IllegalArgumentException
	 *             if an illegal protocol is used
	 *
	 * @see OSCChannel#UDP
	 * @see OSCChannel#TCP
	 * @see #getLocalAddress
	 *
	 * @since NetUtil 0.33
	 */
	public static OSCServer newUsing(OSCPacketCodec c, String protocol)
			throws IOException {
		return newUsing(c, protocol, 0);
	}

	/**
	 * Creates a new instance of an <code>OSCServer</code>, using default codec
	 * and a specific transport protocol and port. It uses the local machine's
	 * IP.
	 * <p>
	 * Note that the <code>port</code> specifies the local socket (at which the
	 * server listens and from which it sends, or in the case of TCP transport,
	 * from which it establishes client connections), it does not determine the
	 * remote sockets. The address of a remote client communicating to this
	 * server is passed in the <code>messageReceived</code> method of any
	 * registered <code>OSCListener</code>, and must be picked up and handed in
	 * to the <code>send</code> method to reply back to the client!
	 *
	 * @param protocol
	 *            the protocol to use, currently either <code>UDP</code> or
	 *            <code>TCP</code>
	 * @param port
	 *            the port number for the OSC socket, or <code>0</code> to use
	 *            an arbitrary free port
	 * @return the newly created server
	 *
	 * @throws IOException
	 *             if a networking error occurs while creating the socket
	 * @throws IllegalArgumentException
	 *             if an illegal protocol is used
	 */
	public static OSCServer newUsing(String protocol, int port)
			throws IOException {
		return newUsing(OSCPacketCodec.getDefaultCodec(), protocol, port);
	}

	/**
	 * Creates a new instance of an <code>OSCServer</code>, using a specific
	 * codec and transport protocol and port. It uses the local machine's IP.
	 * <p>
	 * Note that the <code>port</code> specifies the local socket (at which the
	 * server listens and from which it sends, or in the case of TCP transport,
	 * from which it establishes client connections), it does not determine the
	 * remote sockets. The address of a remote client communicating to this
	 * server is passed in the <code>messageReceived</code> method of any
	 * registered <code>OSCListener</code>, and must be picked up and handed in
	 * to the <code>send</code> method to reply back to the client!
	 *
	 * @param c
	 *            the codec to use
	 * @param protocol
	 *            the protocol to use, currently either <code>UDP</code> or
	 *            <code>TCP</code>
	 * @param port
	 *            the port number for the OSC socket, or <code>0</code> to use
	 *            an arbitrary free port
	 * @return the newly created server
	 *
	 * @throws IOException
	 *             if a networking error occurs while creating the socket
	 * @throws IllegalArgumentException
	 *             if an illegal protocol is used
	 *
	 * @since NetUtil 0.33
	 */
	public static OSCServer newUsing(OSCPacketCodec c, String protocol, int port)
			throws IOException {
		return newUsing(c, protocol, port, false);
	}

	/**
	 * Creates a new instance of an <code>OSCServer</code>, using default codec
	 * and a specific transport protocol and port. It uses the local machine's
	 * IP or the &quot;loopback&quot; address.
	 * <p>
	 * Note that the <code>port</code> specifies the local socket (at which the
	 * server listens and from which it sends, or in the case of TCP transport,
	 * from which it establishes client connections), it does not determine the
	 * remote sockets. The address of a remote client communicating to this
	 * server is passed in the <code>messageReceived</code> method of any
	 * registered <code>OSCListener</code>, and must be picked up and handed in
	 * to the <code>send</code> method to reply back to the client!
	 *
	 * @param protocol
	 *            the protocol to use, currently either <code>UDP</code> or
	 *            <code>TCP</code>
	 * @param port
	 *            the port number for the OSC socket, or <code>0</code> to use
	 *            an arbitrary free port
	 * @param loopBack
	 *            if <code>true</code>, the &quot;loopback&quot; address (
	 *            <code>&quot;127.0.0.1&quot;</code>) is used which limits
	 *            communication to the local machine. If <code>false</code>, the
	 *            special IP <code>"0.0.0.0"</code> is used which means messages
	 *            from any IP as well as from the loopback are accepted
	 *
	 * @return the newly created server
	 *
	 * @throws IOException
	 *             if a networking error occurs while creating the socket
	 * @throws IllegalArgumentException
	 *             if an illegal protocol is used
	 */
	public static OSCServer newUsing(String protocol, int port, boolean loopBack)
			throws IOException {
		return newUsing(OSCPacketCodec.getDefaultCodec(), protocol, port,
				loopBack);
	}

	/**
	 * Creates a new instance of an <code>OSCServer</code>, using a specific
	 * codec and transport protocol and port. It uses the local machine's IP or
	 * the &quot;loopback&quot; address.
	 * <p>
	 * Note that the <code>port</code> specifies the local socket (at which the
	 * server listens and from which it sends, or in the case of TCP transport,
	 * from which it establishes client connections), it does not determine the
	 * remote sockets. The address of a remote client communicating to this
	 * server is passed in the <code>messageReceived</code> method of any
	 * registered <code>OSCListener</code>, and must be picked up and handed in
	 * to the <code>send</code> method to reply back to the client!
	 *
	 * @param c
	 *            the codec to use
	 * @param protocol
	 *            the protocol to use, currently either <code>UDP</code> or
	 *            <code>TCP</code>
	 * @param port
	 *            the port number for the OSC socket, or <code>0</code> to use
	 *            an arbitrary free port
	 * @param loopBack
	 *            if <code>true</code>, the &quot;loopback&quot; address (
	 *            <code>&quot;127.0.0.1&quot;</code>) is used which limits
	 *            communication to the local machine. If <code>false</code>, the
	 *            special IP <code>"0.0.0.0"</code> is used which means messages
	 *            from any IP as well as from the loopback are accepted
	 *
	 * @return the newly created server
	 *
	 * @throws IOException
	 *             if a networking error occurs while creating the socket
	 * @throws IllegalArgumentException
	 *             if an illegal protocol is used
	 *
	 * @since NetUtil 0.33
	 */
	public static OSCServer newUsing(OSCPacketCodec c, String protocol,
			int port, boolean loopBack) throws IOException {
		// final InetSocketAddress localAddress = loopBack ? new
		// InetSocketAddress( "127.0.0.1", port ) :
		// new InetSocketAddress( InetAddress.getLocalHost(), port );
		final InetSocketAddress localAddress = new InetSocketAddress(
				loopBack ? "127.0.0.1" : "0.0.0.0", port);
		return newUsing(c, protocol, localAddress);
	}

	/**
	 * Creates a new instance of an <code>OSCServer</code>, using default codec
	 * and a specific transport protocol and local socket address.
	 *
	 * @param protocol
	 *            the protocol to use, currently either <code>UDP</code> or
	 *            <code>TCP</code>
	 * @param localAddress
	 *            a valid address to use for the OSC socket. If the port is
	 *            <code>0</code>, an arbitrary free port is picked when the
	 *            receiver is started. (you can find out the actual port in this
	 *            case by calling <code>getLocalAddress()</code> after the
	 *            server was started).
	 * @return the newly created server
	 *
	 * @throws IOException
	 *             if a networking error occurs while creating the socket
	 * @throws IllegalArgumentException
	 *             if an illegal protocol is used
	 *
	 * @see OSCChannel#UDP
	 * @see OSCChannel#TCP
	 * @see #getLocalAddress
	 *
	 * @since NetUtil 0.39
	 */
	public static OSCServer newUsing(String protocol,
			InetSocketAddress localAddress) throws IOException {
		return newUsing(OSCPacketCodec.getDefaultCodec(), protocol,
				localAddress);
	}

	/**
	 * Creates a new instance of an <code>OSCServer</code>, using a given codec,
	 * a specific transport protocol and local socket address.
	 *
	 * @param c
	 *            the codec to use
	 * @param protocol
	 *            the protocol to use, currently either <code>UDP</code> or
	 *            <code>TCP</code>
	 * @param localAddress
	 *            a valid address to use for the OSC socket. If the port is
	 *            <code>0</code>, an arbitrary free port is picked when the
	 *            receiver is started. (you can find out the actual port in this
	 *            case by calling <code>getLocalAddress()</code> after the
	 *            server was started).
	 * @return the newly created server
	 *
	 * @throws IOException
	 *             if a networking error occurs while creating the socket
	 * @throws IllegalArgumentException
	 *             if an illegal protocol is used
	 *
	 * @see OSCChannel#UDP
	 * @see OSCChannel#TCP
	 * @see #getLocalAddress
	 *
	 * @since NetUtil 0.39
	 */
	public static OSCServer newUsing(OSCPacketCodec c, String protocol,
			InetSocketAddress localAddress) throws IOException {
		if (protocol.equals(UDP)) {
			return new UDPOSCServer(c, localAddress);
		} else if (protocol.equals(TCP)) {
			return new TCPOSCServer(c, localAddress);
		} else {
			throw new IllegalArgumentException(
					NetUtil.getResourceString("errUnknownProtocol") + protocol);
		}
	}

	public String getProtocol() {
		return protocol;
	}

	/**
	 * Queries the server socket's address. This is the address at which the
	 * server accepts connections (when using TCP) or receives and sends
	 * messages (when using UDP). You can determine the host and port from the
	 * returned address by calling <code>getHostName()</code> (or for the IP
	 * <code>getAddress().getHostAddress()</code>) and <code>getPort()</code>.
	 * <p>
	 * Note that if the server is bound to the accept-any IP
	 * <code>"0.0.0.0"</code>, which happens for example when calling
	 * <code>newUsing( &lt;protocol&gt;, 0, false )</code>, the returned IP will
	 * be the localhost's IP, so you can patch the result directly into any
	 * <code>setTarget</code> call.
	 *
	 * @return the address of the server's local socket.
	 *
	 * @throws IOException
	 *             if the local host could not be resolved
	 *
	 * @see java.net.InetSocketAddress#getHostName()
	 * @see java.net.InetSocketAddress#getAddress()
	 * @see java.net.InetSocketAddress#getPort()
	 *
	 * @see #getProtocol()
	 */
	public abstract InetSocketAddress getLocalAddress() throws IOException;

	/**
	 * Sends an OSC packet (bundle or message) to the given network address. The
	 * address should correspond to one of the connected clients. Particularly,
	 * in <code>TCP</code> mode, trying to send to a client which is not
	 * connected will throw an exception. In a future version of NetUtil, there
	 * will be an interface to detect clients connecting and disconnecting. For
	 * now, clients can be implicitly detected by a registered
	 * <code>OSCListener</code>.
	 *
	 * @param p
	 *            the packet to send
	 * @param target
	 *            the target address to send the packet to
	 *
	 * @throws IOException
	 *             if a write error, OSC encoding error, buffer overflow error
	 *             or network error occurs, if no client connection for the
	 *             given address exists
	 */
	public abstract void send(OSCPacket p, SocketAddress target)
			throws IOException;

	/**
	 * Registers a listener that gets informed about incoming messages (from any
	 * of the connected clients). You can call this both when the server is
	 * active or inactive.
	 *
	 * @param listener
	 *            the listener to register
	 */
	public abstract void addOSCListener(OSCListener listener);

	/**
	 * Unregisters a listener that gets informed about incoming messages
	 *
	 * @param listener
	 *            the listener to remove from the list of notified objects.
	 */
	public abstract void removeOSCListener(OSCListener listener);

	/**
	 * Starts the server. The server becomes attentive to requests for
	 * connections from clients, starts to receive OSC messages and is able to
	 * reply back to connected clients.
	 *
	 * @throws IOException
	 *             if a networking error occurs
	 */
	public abstract void start() throws IOException;

	/**
	 * Checks whether the server is active (was started) or not (is stopped).
	 *
	 * @return <code>true</code> if the server is active, <code>false</code>
	 *         otherwise
	 */
	public abstract boolean isActive();

	/**
	 * Stops the server. For <code>TCP</code> mode, this implies that all client
	 * connections are closed. Stops listening for incoming OSC traffic.
	 *
	 * @throws IOException
	 *             if a networking error occurs
	 */
	public abstract void stop() throws IOException;

	public abstract void setBufferSize(int size);

	public abstract int getBufferSize();

	public void setCodec(OSCPacketCodec c) {
		defaultCodec = c;
	}

	public OSCPacketCodec getCodec() {
		return defaultCodec;
	}

	/**
	 * Specifies which codec is used in packet coding and decoding for a given
	 * client socket.
	 *
	 * @param c
	 *            the codec to use
	 * @param target
	 *            the client's address for whom the codec is changed
	 * @throws IOException
	 *             if a networking error occurs or the client does not exist
	 *
	 * @since NetUtil 0.33
	 */
	public abstract void setCodec(OSCPacketCodec c, SocketAddress target)
			throws IOException;

	/**
	 * Queries the codec used in packet coding and decoding for a given client
	 * socket.
	 *
	 * @param target
	 *            the client's address for whom the codec is queried
	 * @return the current codec of this channel
	 * @throws IOException
	 *             if a networking error occurs or the client does not exist
	 *
	 * @see OSCPacketCodec#getDefaultCodec()
	 *
	 * @since NetUtil 0.33
	 */
	public abstract OSCPacketCodec getCodec(SocketAddress target)
			throws IOException;

	public final void dumpOSC(int mode, PrintStream stream) {
		dumpIncomingOSC(mode, stream);
		dumpOutgoingOSC(mode, stream);
	}

	public abstract void dumpIncomingOSC(int mode, PrintStream stream);

	public abstract void dumpOutgoingOSC(int mode, PrintStream stream);

	/**
	 * Destroys the server and frees resources associated with it. This
	 * automatically stops the server and closes the networking channels. Do not
	 * use this server instance any more after calling <code>dispose.</code>
	 */
	public abstract void dispose();

	protected InetSocketAddress getLocalAddress(InetAddress addr, int port)
			throws UnknownHostException {
		return new InetSocketAddress(
				addr.getHostName().equals("0.0.0.0") ? InetAddress.getLocalHost()
						: addr, port);
	}

	private static class UDPOSCServer extends OSCServer {
		// private final Map mapCodecs = new HashMap(); // key = SocketAddress
		// (remote), value = OSCPacketCodec
		private final OSCReceiver rcv;
		private final OSCTransmitter trns;

		protected UDPOSCServer(OSCPacketCodec c, InetSocketAddress localAddress)
				throws IOException {
			super(c, UDP);
			rcv = OSCReceiver.newUsing(c, UDP, localAddress);
			trns = OSCTransmitter.newUsing(c, UDP, localAddress);
		}

		public InetSocketAddress getLocalAddress() throws IOException {
			return rcv.getLocalAddress();
		}

		public void addOSCListener(OSCListener listener) {
			rcv.addOSCListener(listener);
		}

		public void removeOSCListener(OSCListener listener) {
			rcv.removeOSCListener(listener);
		}

		public void setCodec(OSCPacketCodec c) {
			rcv.setCodec(c);
			trns.setCodec(c);
			super.setCodec(c);
		}

		public void setCodec(OSCPacketCodec c, SocketAddress target)
				throws IOException {
			throw new IOException("Not supported in UDP mode");
		}

		public OSCPacketCodec getCodec(SocketAddress target) throws IOException {
			throw new IOException("Not supported in UDP mode");
		}

		public void start() throws IOException {
			if (!trns.isConnected()) {
				trns.connect();
				rcv.setChannel(trns.getChannel());
			}
			rcv.startListening();
		}

		public void stop() throws IOException {
			rcv.stopListening();
		}

		public boolean isActive() {
			return rcv.isListening();
		}

		public void send(OSCPacket p, SocketAddress target) throws IOException {
			trns.send(p, target);
		}

		// protected SelectableChannel getChannel()
		// {
		// return rcv.getChannel();
		// }
		public void dispose() {
			rcv.dispose();
			trns.dispose();
		}

		public void setBufferSize(int size) {
			rcv.setBufferSize(size);
			trns.setBufferSize(size);
		}

		public int getBufferSize() {
			return rcv.getBufferSize();
		}

		public void dumpIncomingOSC(int mode, PrintStream stream) {
			rcv.dumpOSC(mode, stream);
		}

		public void dumpOutgoingOSC(int mode, PrintStream stream) {
			trns.dumpOSC(mode, stream);
		}
	}

	private static class TCPOSCServer extends OSCServer implements Runnable,
			OSCListener {
		private final Map mapRcv = new HashMap(); // key = SocketAddress
													// (remote), value =
													// OSCReceiver
		private final Map mapTrns = new HashMap(); // key = SocketAddress
													// (remote), value =
													// OSCTransmitter
		private final List collListeners = new ArrayList();
		private Thread thread = null;
		private final Object startStopSync = new Object(); // mutual exclusion
															// startListening /
															// stopListening
		private final Object threadSync = new Object(); // communication with
														// receiver thread
		private final Object connSync = new Object(); // syncs mapRcv and
														// mapTrns
		private boolean isListening = false;
		private int bufSize = DEFAULTBUFSIZE;
		private int inMode = kDumpOff;
		private int outMode = kDumpOff;
		private PrintStream inStream = null;
		private PrintStream outStream = null;
		// private final InetSocketAddress localAddress;
		private final ServerSocketChannel ssch;

		protected TCPOSCServer(OSCPacketCodec c, InetSocketAddress localAddress)
				throws IOException {
			super(c, TCP);
			// this.localAddress = localAddress;
			ssch = ServerSocketChannel.open();
			ssch.socket().bind(localAddress);
		}

		public InetSocketAddress getLocalAddress() throws IOException {
			final ServerSocket ss = ssch.socket();
			return getLocalAddress(ss.getInetAddress(), ss.getLocalPort());
			// return new InetSocketAddress( ssch.socket().getInetAddress(),
			// ssch.socket().getLocalPort() );
			// return localAddress;
		}

		public void addOSCListener(OSCListener listener) {
			synchronized (collListeners) {
				collListeners.add(listener);
			}
		}

		public void removeOSCListener(OSCListener listener) {
			synchronized (collListeners) {
				collListeners.remove(listener);
			}
		}

		public void setCodec(OSCPacketCodec c) {
			OSCTransmitter trns;
			synchronized (connSync) {
				for (Iterator iter = mapTrns.values().iterator(); iter
						.hasNext();) {
					trns = (OSCTransmitter) iter.next();
					if (trns.getCodec() == defaultCodec) {
						trns.setCodec(c);
					}
				}
			}
			super.setCodec(c);
		}

		public void setCodec(OSCPacketCodec c, SocketAddress target)
				throws IOException {
			final OSCTransmitter trns;
			synchronized (connSync) {
				trns = (OSCTransmitter) mapTrns.get(target);
			}
			if (trns == null)
				throw new NotYetConnectedException();
		}

		public OSCPacketCodec getCodec(SocketAddress target) throws IOException {
			final OSCTransmitter trns;
			synchronized (connSync) {
				trns = (OSCTransmitter) mapTrns.get(target);
			}
			if (trns == null)
				throw new NotYetConnectedException();
			return trns.getCodec();
		}

		public void start() throws IOException {
			synchronized (startStopSync) {
				if (Thread.currentThread() == thread)
					throw new IllegalStateException(
							"Cannot call startListening() in the server body thread");
				if (isListening && ((thread == null) || !thread.isAlive())) {
					isListening = false;
				}
				if (!isListening) {
					isListening = true;
					thread = new Thread(this, "TCPServerBody");
					thread.setDaemon(true);
					thread.start();
				}
			}
		}

		public void stop() throws IOException {
			synchronized (startStopSync) {
				if (Thread.currentThread() == thread)
					throw new IllegalStateException(
							"Cannot call stopListening() in the server body thread");
				if (isListening) {
					isListening = false;
					if ((thread != null) && thread.isAlive()) {
						try {
							synchronized (threadSync) {
								final SocketChannel guard;
								guard = SocketChannel.open();
								guard.connect(ssch.socket()
										.getLocalSocketAddress());
								guard.close();
								// try {
								//
								// }
								// finally {
								// try { guard.close(); } catch( IOException e1
								// ) {}
								// }
								// guard.send( guardPacket );
								threadSync.wait(5000);
							}
						} catch (InterruptedException e2) {
							System.err.println(e2.getLocalizedMessage());
						} catch (IOException e1) {
							System.err.println("TCPServerBody.stopListening : "
									+ e1);
							throw e1;
						} finally {
							// guard = null;
							if ((thread != null) && thread.isAlive()) {
								try {
									System.err
											.println("TCPServerBody.stopListening : rude task killing ("
													+ this.hashCode() + ")");
									ssch.close(); // rude task killing
								} catch (IOException e3) {
									System.err
											.println("TCPServerBody.stopListening 2: "
													+ e3);
								}
							}
							thread = null;
							stopAll();
						}
					}
				}
			}
		}

		public boolean isActive() {
			return isListening;
		}

		public void send(OSCPacket p, SocketAddress target) throws IOException {
			final OSCTransmitter trns;
			synchronized (connSync) {
				trns = (OSCTransmitter) mapTrns.get(target);
			}
			if (trns == null)
				throw new NotYetConnectedException();
			trns.send(p);
		}

		// protected SelectableChannel getChannel()
		// {
		// return ssch;
		// }
		public void dispose() {
			try {
				stop();
			} catch (IOException e1) { /* ignored */
			}
			try {
				ssch.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}

		private void stopAll() {
			synchronized (connSync) {
				for (Iterator iter = mapRcv.values().iterator(); iter.hasNext();) {
					((OSCReceiver) iter.next()).dispose();
				}
				mapRcv.clear();
				for (Iterator iter = mapTrns.values().iterator(); iter
						.hasNext();) {
					((OSCTransmitter) iter.next()).dispose();
				}
				mapTrns.clear();
			}
		}

		public void setBufferSize(int size) {
			synchronized (connSync) {
				bufSize = size;
				for (Iterator iter = mapRcv.values().iterator(); iter.hasNext();) {
					((OSCReceiver) iter.next()).setBufferSize(size);
				}
				for (Iterator iter = mapTrns.values().iterator(); iter
						.hasNext();) {
					((OSCTransmitter) iter.next()).setBufferSize(size);
				}
			}
		}

		public int getBufferSize() {
			synchronized (connSync) {
				return bufSize;
			}
		}

		public void dumpIncomingOSC(int mode, PrintStream stream) {
			synchronized (connSync) {
				inMode = mode;
				inStream = stream;
				for (Iterator iter = mapRcv.values().iterator(); iter.hasNext();) {
					((OSCReceiver) iter.next()).dumpOSC(mode, stream);
				}
			}
		}

		public void dumpOutgoingOSC(int mode, PrintStream stream) {
			synchronized (connSync) {
				outMode = mode;
				outStream = stream;
				for (Iterator iter = mapTrns.values().iterator(); iter
						.hasNext();) {
					((OSCTransmitter) iter.next()).dumpOSC(mode, stream);
				}
			}
		}

		public void run() {
			SocketAddress sender;
			SocketChannel sch;
			OSCReceiver rcv;
			OSCTransmitter trns;
			try {
				listen: while (isListening) {
					try {
						sch = ssch.accept();
						if (!isListening)
							break listen;
						if (sch == null)
							continue listen;
						sender = sch.socket().getRemoteSocketAddress();
						synchronized (connSync) {
							rcv = OSCReceiver.newUsing(defaultCodec, sch);
							rcv.setBufferSize(bufSize);
							mapRcv.put(sender, rcv);
							trns = OSCTransmitter.newUsing(defaultCodec, sch);
							trns.setBufferSize(bufSize);
							// System.err.println ("put "+sender );
							mapTrns.put(sender, trns);
							rcv.dumpOSC(inMode, inStream);
							trns.dumpOSC(outMode, outStream);
							rcv.addOSCListener(this);
							rcv.startListening();
						}
					} catch (ClosedChannelException e11) { // bye bye, we have
															// to quit
						if (isListening) {
							System.err.println(e11);
						}
						return;
					} catch (IOException e1) {
						if (isListening) {
							System.err.println(new OSCException(
									OSCException.RECEIVE, e1.toString()));
						}
					}
				} // while( isListening )
			} finally {
				synchronized (threadSync) {
					thread = null;
					threadSync.notifyAll(); // stopListening() might be waiting
				}
			}
		}

		public void messageReceived(OSCMessage msg, SocketAddress sender,
				long time) {
			OSCListener listener;
			synchronized (collListeners) {
				for (int i = 0; i < collListeners.size(); i++) {
					listener = (OSCListener) collListeners.get(i);
					listener.messageReceived(msg, sender, time);
				}
			}
		}
	}
}
